﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reactive.Linq;
using System.Windows.Forms;
using Cubicle.Nuget.Core;
using Cubicle.Nuget.Functions;
using NuGet;

namespace Cubicle.Nuget.Gui
{
    public partial class PackageBrowserUserControl : UserControl
    {
        private IObservable<IEnumerable<IPackage>> _foundPackages;
        private IDisposable _subscriber;

        private const int Take = 20;
        private int _skip = 0;

        public event EventHandler RefreshPackages;

        public PackageBrowserUserControl()
        {
            InitializeComponent();
            InitializeObservables();
            PopulateDropdowns();
        }

        private void PopulateDropdowns()
        {
            if (NugetFunctions.FeedManager == null) return;
            FeedComboBox.DataSource = NugetFunctions
                .FeedManager
                .Select(x => x.Name)
                .ToList();

            FeedComboBox.Refresh();
        }

        private void InitializeObservables()
        {
            FoundPackageGrid.AutoGenerateColumns = true;
            
            var nextClick = 
                    Observable.FromEventPattern(NextButton, "Click")
                    .Do(_ => { _skip += Take; });

            var previousClick = 
                    Observable.FromEventPattern(PreviousButton, "Click")
                    .Do(_ => { _skip = Math.Max(0, _skip - Take); });

            var reset = 
                    Observable.FromEventPattern(FeedComboBox, "SelectedValueChanged").Merge(
                    Observable.FromEventPattern(SourceComboBox, "SelectedValueChanged")).Merge(
                    Observable.FromEventPattern(SearchCheckBox, "Click")).Merge(
                    Observable.FromEventPattern(this, "RefreshPackages")
                )
                      .Do(_ => { _skip = 0; });
            
            _foundPackages = Observable
                .FromEventPattern(SearchTextBox, "TextChanged")
                .Merge(nextClick)
                .Merge(previousClick)
                .Merge(reset)
                .Select(_ => SearchTextBox.Text)
                .Throttle(TimeSpan.FromMilliseconds(2000))
                .Where(text => AllowEmptyKeyword || text.Length >= 3)
                .Select(FindPackage);

            _subscriber = _foundPackages
                .ObserveOn(FoundPackageGrid)
                .Subscribe(ProcessPackages);
        }

        private bool AllowEmptyKeyword
        {
            get { return IsLocalSelected || (SelectedClient != null && SelectedClient.IsLocal); }
        }

        private bool IsLocalSelected { get { return SourceComboBox.Text.Equals("Local"); } }

        private void ProcessPackages(IEnumerable<IPackage> packages)
        {
            var dataSource = packages.Select(p =>
                    new
                    {
                        Id = p.Id,
                        Version = p.Version.ToString(),
                        Title = p.Title,
                        Description = p.Description
                    }
                ).ToList();
            FoundPackageGrid.DataSource = dataSource;
            FoundPackageGrid.Refresh();
            StatusLabel.Text = "Done.";
        }

        private IEnumerable<IPackage> FindPackage(string searchTerm)
        {
            StatusLabel.Text = "Searching...";
            var results = SearchCheckBox.Checked
                ? SelectedRepository.Search(searchTerm, false)
                : SelectedRepository.FindPackages(new List<string> {searchTerm});
            return results
                .Skip(_skip)
                .Take(Take);
        }

        private IPackageRepository SelectedRepository
        {
            get
            {
                return IsLocalSelected 
                    ? SelectedClient.Manager.LocalRepository
                    : SelectedClient.Manager.SourceRepository;
            }
        }

        private NugetClient SelectedClient
        {
            get
            {
                if (FeedComboBox.SelectedItem == null) return null;
                var name = FeedComboBox.SelectedItem.ToString();
                return NugetFunctions.FeedManager[name];
            }
        }

        private IPackage SelectedPackage
        {
            get
            {
                if (FoundPackageGrid.SelectedCells.Count == 0 || SelectedClient == null) return null;
                var rowId = FoundPackageGrid.SelectedCells[0].RowIndex;
                var id = FoundPackageGrid.Rows[rowId].Cells["Id"].Value.ToString();
                var version = FoundPackageGrid.Rows[rowId].Cells["Version"].Value.ToString();
                return SelectedClient[id, version];
            }
        }

        private void SourceComboBox_SelectedValueChanged(object sender, EventArgs e)
        {
            var isLocal = SourceComboBox.Text.Equals("Local");
            UninstallButton.Visible = isLocal;
            InstallButton.Visible = !isLocal;
        }

        private void InstallButton_Click(object sender, EventArgs e)
        {
            var package = SelectedPackage;
            if (package == null) return;
            SelectedClient.InstallPackage(package.Id, package.Version.ToString());
            RefreshPackages(sender, e);
        }

        private string ToStringIds<T>(List<T> items, Func<T, string> conv, StringBuilder sb = null)
        {
            if (sb == null) sb = new StringBuilder();
            if (items.Count() == 0) return sb.ToString();
            sb.Append(items.First());
            if (items.Count() > 1) sb.Append(", ");
            items.RemoveAt(0);
            return ToStringIds(items, conv, sb);
        }

        private void UninstallButton_Click(object sender, EventArgs e)
        {
            var package = SelectedPackage;
            if (package == null) return;
            if (HasDependents(package)) return;
            var removeDependencies = RemoveDependencies(package);
            SelectedClient.UninstallPackage(package.Id, package.Version.ToString(), removeDependencies);
            RefreshPackages(sender, e);
        }

        private bool HasDependents(IPackage package)
        {
            var dependents = package
                .GetSupportedFrameworks()
                .Select(framework => new DependentsWalker(SelectedClient.Repository, framework))
                .SelectMany(walker => walker.GetDependents(package))
                .Distinct()
                .ToList();
            if (dependents.Count() == 0) return false;
            var depStr = ToStringIds(dependents, p => p.Id + " " + p.Version.ToString());
            MessageBox.Show(String.Format("Other packages depend on this one. Cannot uninstall. Dependents = {0}", depStr),"Error");
            return true;
        }

        private bool RemoveDependencies(IPackage package)
        {
            var dep = package
                .GetSupportedFrameworks()
                .SelectMany(f => package.GetCompatiblePackageDependencies(f))
                .Distinct()
                .ToList();
            if (dep.Count() == 0) return false;
            var depStr = ToStringIds(dep, p => p.Id + " " + p.VersionSpec.ToString());
            return MessageBox.Show(String.Format("Uninstall dependencies? Dependencies = {0}", depStr), "Uninstall", MessageBoxButtons.YesNo) == DialogResult.Yes;
        }

    }
}
